<?php
/**
 *  Generic Item: a class for property-based model value management
 *  Similar to Active Record pattern
 *  Implements ArrayAccess: model[index] returns the value of property 'index'
 *
 *
 */
namespace Yz\modules\Generic;

use Yz_Hash;

class Item_Default
    extends \Yz_Object
    implements \ArrayAccess,\Yz_Item_Interface
{

    /** The descriptor, one per item class (common for all items of the class)
     *
     *  Should never be empty
     *
     *  @var Yz_Descriptor_Interface
     */
    protected static $descriptor;

    /** The model
     *
     *  Should never be empty
     *
     *  @var Yz_Model_Interface
     */
    protected $model;

    /** The array of values
     *  @var array
     */
    protected $values = array();




    /****************************
     *      Public methods      *
     ****************************/




    /** item constructor
     *
     *
     */
    public function __construct( \Yz_Model_Interface $model )
    {
        debug();
        $this->model = $model;
        $this->createDescriptor();
    }


    /**
     *  Effectively, get a copy of the list of properties (format: {name: object})
     *  NOTE: requires static $descriptor in every descendant class
     *  @return Yz_Hash of properties
     */
    public function describe()
    {
        debug();
        return $this::$descriptor;
    }


    /**
     *  get item's values as hash
     *  @return array
     */
    public function toArray()
    {
        return $this->values;
    }

    /**
     *  set item's values from a hash
     *  The following policies are possible:
     *      - strict: all the properties must be present in input
     *      - relaxed (used here): consider only the input values matching properties,
     *          if no value for a property is supplied, and no value is set,
     *          set its value to a default
     *      - simple: change only the values which are supplied in input, missing properties untouched
     *  @param Array-type
     *  @return this
     */
    public function assignValues( $input )
    {
        debug();
        $input = new Yz_Hash($input);
        foreach ($this::$descriptor as $propName => $prop) {
            if ($input->offsetExists($propName)) {
                $inValue = $input[$propName];
                $this->values[$propName] = $prop->assign($inValue);
            } else if (! array_key_exists($propName, $this->values)) {
                $this->values[$propName] = $prop->assign(null);
            }
        }
        return $this;
    }


    /**
    *   Default action to test for a variable w/o getter
    *   TODO In fact, the presence of a property does not ensure that it "exists", eg. it may be private.
    */
    protected function indexExists( $var )
    {
        return $this::$descriptor->offsetExists($var);
    } // indexExists


    /**
    *   Default action to get a variable w/o getter
    *   TODO In fact, the presence of a property does not ensure that it "exists", eg. it may be private.
    */
    protected function defaultGetter( $var )
    {
        if ($this->indexExists($var)) {
            if (! array_key_exists($var, $this->values)) {
                $this->values[$var] = null;
            }
            return $this->values[$var];
        }
        return $this->undefinedVariable( $var, 'get' );
    } // defaultGetter

    /**
    *   Default action to set a variable w/o setter
    *   TODO In fact, the presence of a property does not ensure that it "exists", eg. it may be private.
    *
    */
    protected function defaultSetter( $var, $value )
    {
        #if ( var in_descriptor ) :
        #   props[var].value = value
        #   return this
        if ($this->indexExists($var)) {
            $this->values[$var] = $this::$descriptor[$var]->assign($value);
            return $this;
        }
        return $this->undefinedVariable( $var, 'set' );
    } // defaultSetter

    /**
     *
     */
    protected function createDescriptor()
    {
        $dsc =& static::$descriptor;
        if (null === $dsc) {
            $config = $this->model
                ->fetchItemConfig($this)
                ->fetchSection("descriptor")
                ;
            if (null === $config) {
                throw new \Yz_Exception("Missing descriptor configuration for {$this}");
            }
            $dsc = \Yz_Descriptor_Default::create($config);
        }
        return $dsc;
    }


    /**
     *  this function provides static::$descriptor into all descendant classes
     *  @return PHP code
     */
    protected static function virtualClassContent()
    {
        return <<<code
    protected static \$descriptor;\n
code;
    }

}
